#include "camera.hh"
#include "distance.hh"
#include <glm/gtx/rotate_vector.hpp>
#include <cstdio>

namespace gl
{
	namespace
	{
		constexpr float small = 1e-4;
		bool is_zero(float x) { return (x*x < small); }

		void prevent_collision(glm::vec3 & pos, glm::vec3 const& dir)
		{
			auto relaxation_count = 10;
			while (relaxation_count-- > 0) {
				auto d = distance::estimate(pos) - 0.05f;
				if (d > small) break;
				pos -= (d * 0.5f - small) * distance::normal(pos);
			}
		}

		float raymarch(glm::vec3 const& pos, glm::vec3 const& dir,
				float max_dist=1e3, float max_iter=100)
		{
			float dist{};
			for (int i=0; i<max_iter && dist < max_dist; i++) {
				auto d = distance::estimate(pos + dist*dir);
				if (d < small) break;
				dist += d;
			}
			return (dist < max_dist ? dist : max_dist);
		}
	}

	void camera::xmove(float speed)
	{
		if (is_zero(speed)) return;
		auto& p = position.target();
		auto o = m * glm::vec3{1, 0, 0};
		o.y = 0;
		o = normalize(o)*speed;
		p += o;
		prevent_collision(p, normalize(o));
	}

	void camera::ymove(float speed)
	{
		if (is_zero(speed)) return;
		auto& p = position.target();
		auto o = glm::vec3{0, speed, 0};
		p += o;
		prevent_collision(p, normalize(o));
	}

	void camera::zmove(float speed)
	{
		if (is_zero(speed)) return;
		auto& p = position.target();
		auto o = m * glm::vec3{0, 0, 1};
		o.y = 0;
		o = normalize(o) * speed;
		p += o;
		prevent_collision(p, normalize(o));
	}

	void camera::rotate(glm::vec3 const& offset)
	{
		auto& rot = rotation.target();
		rot += offset;
		auto rad89 = glm::radians(89.0f);
		rot.x = glm::clamp(rot.x, -rad89, rad89);
	}

	void camera::update(float speed)
	{
		rotation.update(speed);
		position.update(speed);
		distance.update(speed);

		auto& rot = rotation.get();
		auto z = glm::rotateY(glm::rotateX(glm::vec3{0, 0, 1}, rot.x), rot.y);
		auto y = glm::vec3{glm::sin(rot.z), glm::cos(rot.z), 0};
		auto x = normalize(cross(y, z));
		y = cross(z, x);
		m = {x, y, z};

		auto& d = distance.get();
		auto dir = normalize(m*d);
		eye_dist_ = raymarch(position, normalize(m*d), length(d)) - 0.05f;
		if (eye_dist_ < 0.05f) eye_dist_ = -0.05f;
		fprintf(stderr, "eye-dist = %.2f\n", eye_dist_);
		eye_ = position.get() + eye_dist_ * dir;
	}
}

